package at.technikum_wien.embsys.aat.PriscillaCore.component;

import java.util.Iterator;
import java.util.List;

import at.technikum_wien.embsys.aat.PriscillaCore.enums.BinaryState;
import at.technikum_wien.embsys.aat.PriscillaCore.enums.EnoceanComponentType;
import at.technikum_wien.embsys.aat.PriscillaCore.enums.FanState;
import at.technikum_wien.embsys.aat.PriscillaCore.enums.FanType;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventBinary;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventFan;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventIllumination;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventSetPoint;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventTemperature;
import at.technikum_wien.embsys.aat.PriscillaCore.link.event.LinkListener;
import at.technikum_wien.embsys.aat.PriscillaCore.telegram.Telegram;



/**
 * 
 * EEP_07_10_1C component class - room operating panel
 *
 * Type: Temperature sensor, illumination, illumination set point, fan speed and occupancy
 * Temperature max: 40°C
 * Temperature min: 0°C
 * Illumination set point: 0-1000lx
 * Illumination max: 1000lx
 * Illumination min: 0lx
 * Fan speed: 5 Stage + AUTO
 * Occupancy button
 * 
 * @author B. Aigner
 * 
 */
public class EEP_07_10_1C implements EnoceanComponent {
	
	Telegram telegram;
	List<?> eventListener;
	
	float illumination;
	float illuminationSetPoint;
	float temperature;
	FanState fanspeed = FanState.INITIAL_STATE;
	FanType fantype = FanType.INITIAL_STATE;
	BinaryState occupancy = BinaryState.UNKNOWN;
	boolean occupancyAvailable = false;
	
	EnoceanComponentType enoceanComponentType = EnoceanComponentType.EEP_07_10_1C;
	boolean telegramCreation = false;
	
	int illumination_temp;
	float illumination_max = 1000;
	float illumination_min = 0;
	int illumination_value_max = 250;
	int illumination_value_min = 0;
	
	int illumination_set_point_temp;
	float illumination_set_point_max = 1000;
	float illumination_set_point_min = 0;
	int illumination_set_point_value_max = 250;
	int illumination_set_point_value_min = 0;
	
	int temperature_temp;
	float temperature_max = 0;
	float temperature_min = 40;
	int temperature_value_max = 250;
	int temperature_value_min = 0;
	
	float multiplier;

	/**
	 * This constructor is used for received packets.
	 * It will parse the telegram and set the corresponding members.
	 * 
	 * @param telegram Received Telegram
	 * @param listener Event subscriber list
	 */
	public EEP_07_10_1C(Telegram telegram, List<?> listener) {
		super();
		this.telegram = telegram;
		this.eventListener = listener;
		
		illumination_temp = telegram.getData3();
		illumination_set_point_temp = telegram.getData2();
		temperature_temp = telegram.getData1();
		
		fantype = FanType.TYPE5;
		switch((telegram.getData0() & 0x70) / 16) {
		case 0:
			fanspeed = FanState.AUTO;
			break;
		case 1:
			fanspeed = FanState.SPEED0;
			break;
		case 2:
			fanspeed = FanState.SPEED1;
			break;
		case 3:
			fanspeed = FanState.SPEED2;
			break;
		case 4:
			fanspeed = FanState.SPEED3;
			break;
		case 5:
			fanspeed = FanState.SPEED4;
			break;
		case 6:
			fanspeed = FanState.SPEED5;
			break;
		case 7:
			fanspeed = FanState.OFF;
			break;
		}

		if(!(checkBit(telegram.getData0(), 1))) {
			occupancyAvailable = true;
			if(checkBit(telegram.getData0(), 0)) {
				occupancy = BinaryState.FALSE;
			} else {
				occupancy = BinaryState.TRUE;
			}
		}

		multiplier = (illumination_set_point_max - illumination_set_point_min) / (illumination_set_point_value_max - illumination_set_point_value_min);

		if(multiplier >= 0) {
			this.illuminationSetPoint = multiplier * illumination_set_point_temp + illumination_set_point_max;
		} else {
			this.illuminationSetPoint = multiplier * illumination_set_point_temp + illumination_set_point_min;
		}


		multiplier = (illumination_max - illumination_min) / (illumination_value_max - illumination_value_min);

		if(multiplier >= 0) {
			this.illumination = multiplier * illumination_temp + illumination_max;
		} else {
			this.illumination = multiplier * illumination_temp + illumination_min;
		}


		multiplier = (temperature_max - temperature_min) / (temperature_value_max - temperature_value_min);

		if(multiplier >= 0) {
			this.temperature = multiplier * temperature_temp + temperature_max;
		} else {
			this.temperature = multiplier * temperature_temp + temperature_min;
		}
		fireFrame();
	}

	/**
	 * Fire all associated events
	 */
	void fireFrame() {
		Iterator<?> listen = eventListener.iterator();
		final FrameEventSetPoint frameEventSetPoint = new FrameEventSetPoint(this, telegram.getDeviceID(), this.illuminationSetPoint);
		while (listen.hasNext()) {
			((LinkListener) listen.next()).frameReceived_SetPoint(frameEventSetPoint);
		}
		
		listen = eventListener.iterator();
		final FrameEventFan frameEventFan = new FrameEventFan(this, telegram.getDeviceID(), this.fanspeed, this.fantype );
		while (listen.hasNext()) {
			((LinkListener) listen.next()).frameReceived_Fan(frameEventFan);
		}
		
		listen = eventListener.iterator();
		final FrameEventTemperature frameEventTemperature = new FrameEventTemperature(this, telegram.getDeviceID(), this.temperature);
		while (listen.hasNext()) {
			((LinkListener) listen.next()).frameReceived_Temperature(frameEventTemperature);
		}
		
		if(occupancyAvailable == true) {
			listen = eventListener.iterator();
			final FrameEventBinary frameEventBinary = new FrameEventBinary(this, telegram.getDeviceID(), this.occupancy, BinaryState.UNKNOWN, BinaryState.UNKNOWN, BinaryState.UNKNOWN);
			while (listen.hasNext()) {
				((LinkListener) listen.next()).frameReceived_Binary(frameEventBinary);
			}
		}
		
		listen = eventListener.iterator();
		final FrameEventIllumination frameEventIllumination = new FrameEventIllumination(this, telegram.getDeviceID(), this.illumination);
		while (listen.hasNext()) {
			((LinkListener) listen.next()).frameReceived_Illumination(frameEventIllumination);
		}
	}

	/**
	 * Void constructor. It is used to create a new telegram for sending
	 */
	public EEP_07_10_1C() {
		super();
		telegram = new Telegram();
		telegramCreation = true;
	}

	/**
	 * This constructor is used to create a telegram (the device id is given as parameter)
	 * @param deviceID Full device ID
	 */
	public EEP_07_10_1C(String deviceID) {
		super();
		telegram = new Telegram();
		telegram.setDeviceID(deviceID);
		telegramCreation = true;
	}
	
	boolean checkBit(int value, int bitNr) {
		return (value & (1 << bitNr)) != 0;
	}
	
	int setBit(int value, int bitNr) {
		return value | (1<<bitNr);
	}
	
	int clrBit(int value, int bitNr) {
		return value & (~(1<<bitNr));
	}
	
	@Override
	public String getDeviceID() {
		return telegram.getDeviceID();
	}
	@Override
	public String getORG() {
		return telegram.getxORG();
	}
	@Override
	public EnoceanComponentType getType() {
		return enoceanComponentType;
	}

	@Override
	public void setDeviceID(String deviceID) {
		telegram.setDeviceID(deviceID);
	}

	/**
	 * Creates a new telegram from the component
	 * 
	 * This method creates a new telegram (if the component is initialized with
	 * a constructor without a telegram as parameter) or returns the saved telegram.
	 * The new telegram is built up from the members in this class (in this case the temperature, fan speed
	 * set point (illumination), illumination and occupancy). 
	 * @return Created telegram
	 */
	public Telegram getTelegram() {
		if(telegramCreation == true) {
			multiplier = (temperature_max - temperature_min) / (temperature_value_max - temperature_value_min);
			if(multiplier < 0) {
				temperature_temp = (int)((this.temperature - temperature_max) / multiplier);
			} else {
				temperature_temp = (int)((this.temperature - temperature_min) / multiplier);
			}
			telegram.setData1(temperature_temp);
			
			multiplier = (illumination_set_point_max - illumination_set_point_min) / (illumination_set_point_value_max - illumination_set_point_value_min);
			if(multiplier < 0) {
				illumination_set_point_temp = (int)((this.illuminationSetPoint - illumination_set_point_max) / multiplier);
			} else {
				illumination_set_point_temp = (int)((this.illuminationSetPoint - illumination_set_point_min) / multiplier);
			}
			telegram.setData2(illumination_set_point_temp);
			
			multiplier = (illumination_max - illumination_min) / (illumination_value_max - illumination_value_min);
			if(multiplier < 0) {
				illumination_temp = (int)((this.illumination - illumination_max) / multiplier);
			} else {
				illumination_temp = (int)((this.illumination - illumination_min) / multiplier);
			}
			telegram.setData3(illumination_temp);
			
			switch(fanspeed) {
			case AUTO:
				telegram.setData3(0);
				break;
			case SPEED0:
				telegram.setData3(16);
				break;
			case SPEED1:
				telegram.setData3(32);
				break;
			case SPEED2:
				telegram.setData3(48);
				break;
			case SPEED3:
				telegram.setData3(64);
				break;
			case SPEED4:
				telegram.setData3(80);
				break;
			case SPEED5:
				telegram.setData3(96);
				break;
			case OFF:
				telegram.setData3(112);
				break;
			default:
				return null;
			}
			
			if(occupancy != BinaryState.UNKNOWN) {
				if(occupancy == BinaryState.TRUE) {
					telegram.setData0(clrBit(telegram.getData0(),0));
				} else {
					telegram.setData0(setBit(telegram.getData0(),0));
				}
				telegram.setData0(clrBit(telegram.getData0(),1));
			} else {
				telegram.setData0(setBit(telegram.getData0(),1));
			}
		}
		return telegram;
	}

	public float getIllumination() {
		return illumination;
	}

	public float getIlluminationSetPoint() {
		return illuminationSetPoint;
	}

	public float getTemperature() {
		return temperature;
	}

	public FanState getFanspeed() {
		return fanspeed;
	}

	public FanType getFantype() {
		return fantype;
	}

	public BinaryState getOccupancy() {
		return occupancy;
	}
	
	public void setTemperature(float temperature) {
		if(temperature > temperature_max) return;
		if(temperature < temperature_min) return;
		this.temperature = temperature;
	}
	
	public void setIllumination(float illumination) {
		if(illumination > illumination_max) return;
		if(illumination < illumination_min) return;
		this.illumination = illumination;
	}
	
	public void setOccupancy(BinaryState occupancy) {
		this.occupancy = occupancy;
	}

	public void setFanspeed(FanState fanspeed) {
		this.fanspeed = fanspeed;
	}

	public void setIlluminationSetPoint(float illuminationSetPoint) {
		if(illuminationSetPoint > illumination_set_point_max) return;
		if(illuminationSetPoint < illumination_set_point_min) return;
		this.illuminationSetPoint = illuminationSetPoint;
	}
}
